﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Threading;
using System.Collections.Concurrent;
using System.Collections;

namespace AffinityHelper {
    class ProcessAndCounter {
        public string instanceName;
        public PerformanceCounter counter;

        public ProcessAndCounter(string instanceName, PerformanceCounter counter) {
            this.instanceName = instanceName;
            this.counter = counter;
        }
    }

    class ProcessCPUUsage : IComparable<ProcessCPUUsage> {
        public int pid { get; set; }
        public string instanceName { get; set; }
        public float cpuUsage { get; set; }

        public ProcessCPUUsage(int pid, string instanceName, float cpuUsage) {
            this.pid = pid;
            this.instanceName = instanceName;
            this.cpuUsage = cpuUsage;
        }

        public int CompareTo(ProcessCPUUsage other) {
            return -cpuUsage.CompareTo(other.cpuUsage);
        }
    }

    class ProcessCPUHelper {
        private static readonly ProcessCPUHelper instance = new ProcessCPUHelper();
        ConcurrentDictionary<int, ProcessAndCounter> perfCounterDict = new ConcurrentDictionary<int, ProcessAndCounter>();

        private void refreshInstanceNames() {
            string[] instanceNames = new PerformanceCounterCategory("Process").GetInstanceNames();
            // foreach (string instanceName in instanceNames) {
            Parallel.ForEach(instanceNames, instanceName => {
                try {
                    int processId = (int)new PerformanceCounter("Process", "ID Process", instanceName, true).RawValue;
                    if (processId == 0) return;
                    if (perfCounterDict.ContainsKey(processId) &&
                        perfCounterDict[processId].instanceName == instanceName)
                        return;
                    PerformanceCounter counter = new PerformanceCounter("Process", "% Processor Time", instanceName);
                    perfCounterDict[processId] = new ProcessAndCounter(instanceName, counter);
                } catch (InvalidOperationException) { }
            });
        }

        private void clearExited() {
            List<int> keysToRemove = new List<int>();
            foreach (var pair in perfCounterDict) {
                // if (pair.Value.process.HasExited)
                if (Process.GetProcessById(pair.Key).HasExited)
                    keysToRemove.Add(pair.Key);
            }
            foreach (int key in keysToRemove) {
                perfCounterDict.TryRemove(key, out _);
            }
        }

        public List<ProcessCPUUsage> GetCPUUsages(bool refresh = true) {
            if (refresh) Refresh(false);
            var ret = new List<ProcessCPUUsage>();
            Parallel.ForEach(perfCounterDict, entry => {
                try {
                    int pid = entry.Key;
                    string instanceName = entry.Value.instanceName;
                    PerformanceCounter counter = entry.Value.counter;
                    counter.NextValue();
                    float cValue = counter.NextValue();
                    lock (ret) {
                        ret.Add(new ProcessCPUUsage(pid, instanceName, cValue));
                    }
                } catch { }
            });
            ret.Sort();
            return ret;
        }

        public void Refresh(bool clear_exited = false) {
            refreshInstanceNames();
            if (clear_exited)
                clearExited();
        }

        private ProcessCPUHelper() {
        }

        public static ProcessCPUHelper Instance {
            get {
                return instance;
            }
        }

        public static float getUsageByIdAndName(int pid, string processName) {
            int c = 0;
            while (true) {
                string instanceName;
                if (c > 0) instanceName = processName + "#" + c.ToString();
                else instanceName = processName;
                try {
                    int cPid = (int)new PerformanceCounter("Process", "ID Process", instanceName, true).RawValue;
                    if (cPid != 0 && cPid == pid) {
                        PerformanceCounter counter = new PerformanceCounter("Process", "% Processor Time", instanceName);
                        counter.NextValue();
                        return counter.NextValue();
                    }
                } catch (InvalidOperationException) {
                    break;
                }
                c++;
            }
            return -1;
        }

        public static float getUsageByIdAndName(int pid, string processName, IDictionary<string, int> namedBuffer) {
            string instanceName = processName;
            for (int c = 0; ; c++) {
                if (c > 0) instanceName = processName + "#" + c.ToString();
                if (namedBuffer.ContainsKey(instanceName)) {
                    if (namedBuffer[instanceName] == pid)
                        break;
                    else
                        continue;
                } else {
                    try {
                        int cPid = (int)new PerformanceCounter("Process", "ID Process", instanceName, true).RawValue;
                        if (cPid != 0) {
                            namedBuffer[instanceName] = cPid;
                            if (cPid == pid)
                                break;
                        }
                    } catch (InvalidOperationException) {
                        return -1;
                    }
                }
            }
            PerformanceCounter counter = new PerformanceCounter("Process", "% Processor Time", instanceName);
            counter.NextValue();
            return counter.NextValue();
        }

        /*
        static void Main2() {
            var instanceNames = new PerformanceCounterCategory("Process").GetInstanceNames();
            SortedSet<string> dd = new SortedSet<string>();
            dd.UnionWith(instanceNames);

            var ee = dd.Count(name => name.StartsWith("firefox"));

            var processName = "firefox"; // Replace with the name of the process you want to monitor
            var processId = 64400; // Replace with the process ID you want to monitor
            var instanceName = new PerformanceCounterCategory("Process").GetInstanceNames().FirstOrDefault(
                name => name.StartsWith(processName) && new PerformanceCounter("Process", "ID Process", name, true).RawValue == processId);
            var cpuUsage = new PerformanceCounter("Process", "% Processor Time", instanceName, true);
            Thread.Sleep(1000);
            var firstCall = cpuUsage.NextValue();
            for (int i = 0; i < 5; i++) {
                Thread.Sleep(1000);
                Console.WriteLine(cpuUsage.NextValue() + "%");
            }
            Console.Read();
        }

        static void Main() {
            var cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");

            var processes = Process.GetProcesses();
            var processInfos = new ProcessInfo[processes.Length];
            const bool parallel = true;

                Parallel.ForEach(processes, (process, _, index) => {
                    processInfos[index] = new ProcessInfo {
                        ProcessName = process.ProcessName,
                        // CpuUsage = GetCpuUsageForProcess(cpuCounter, process)
                        CpuUsage = GetCpuUsageForProcess2(process)
                    };
                });

            var topFive = processInfos
                .OrderByDescending(x => x.CpuUsage)
                .Take(5);

            foreach (var process in topFive) {
                Console.WriteLine($"Process: {process.ProcessName} CPU: {process.CpuUsage}");
            }
            Console.ReadKey();
        }

        static float GetCpuUsageForProcess2(Process process) {
            try {
                var pc = new PerformanceCounter("Process", "% Processor Time", process.ProcessName);
                pc.NextValue();
                float v1 = pc.NextValue();
                // float v2 = pc.NextValue();
                float v2 = v1;
                return (v1 + v2) / 2;
            }
            catch (Exception ex) {
                Console.WriteLine("Exception occured when create pc, " + process.ProcessName + ": " + ex.Message);
                return 0;
            }
        }
        */

    }
}